/*!
   \file

   \brief
      Implementation of the cross-platform OpenGL interface from glCP.hpp on Windows OS.

   © 2011 Mark W. Gabby II

   This file is part of Rock Hunter.

   Rock Hunter is free software: you can redistribute it and/or modify it under the terms of the
   GNU General Public License as published by the Free Software Foundation, either version 3 of the
   License, or (at your option) any later version.

   Rock Hunter is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
   even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along with Rock Hunter.
   If not, see <http://www.gnu.org/licenses/>.
*/
#include "Systems/Platform/glCP.hpp"

#define WIN32_LEAN_AND_MEAN
#define WINVER 0x501 // Windows XP
#include <windows.h>

#include <gl/gl.h>
#include <gl/glext.h>
#include <gl/wglext.h>

// Buffer objects :
static PFNGLBINDBUFFERPROC glBindBuffer;
static PFNGLDELETEBUFFERSPROC glDeleteBuffers;
static PFNGLGENBUFFERSPROC glGenBuffers;
static PFNGLISBUFFERPROC glIsBuffer;
static PFNGLBUFFERDATAPROC glBufferData;
static PFNGLBUFFERSUBDATAPROC glBufferSubData;
static PFNGLGETBUFFERSUBDATAPROC glGetBufferSubData;
static PFNGLMAPBUFFERPROC glMapBuffer;
static PFNGLUNMAPBUFFERPROC glUnmapBuffer;
static PFNGLGETBUFFERPARAMETERIVPROC glGetBufferParameteriv;
static PFNGLGETBUFFERPOINTERVPROC glGetBufferPointerv;

// Multitexturing :
static PFNGLACTIVETEXTUREPROC glActiveTexture;
static PFNGLCLIENTACTIVETEXTUREPROC glClientActiveTexture;
static PFNGLMULTITEXCOORD2DPROC glMultiTexCoord2d;
static PFNGLMULTITEXCOORD2DVPROC glMultiTexCoord2dv;
static PFNGLMULTITEXCOORD2FPROC glMultiTexCoord2f;
static PFNGLMULTITEXCOORD2FVPROC glMultiTexCoord2fv;
static PFNGLMULTITEXCOORD2IPROC glMultiTexCoord2i;
static PFNGLMULTITEXCOORD2IVPROC glMultiTexCoord2iv;
static PFNGLMULTITEXCOORD2SPROC glMultiTexCoord2s;
static PFNGLMULTITEXCOORD2SVPROC glMultiTexCoord2sv;

// Point sprites:
static PFNGLPOINTPARAMETERFPROC glPointParameterf;
static PFNGLPOINTPARAMETERIPROC glPointParameteri;
static PFNGLPOINTPARAMETERFVPROC glPointParameterfv;
static PFNGLPOINTPARAMETERIVPROC glPointParameteriv;

PFNWGLSWAPINTERVALEXTPROC p_wglSwapIntervalEXT;
PFNWGLGETSWAPINTERVALEXTPROC p_wglGetSwapIntervalEXT;

static bool gSwapIntervalSupported = false;
static float gAnistrophyLevelSupported = 0;
static bool gVertexBuffersSupported = false;
static bool gMultiTexturingSupported = false;
static bool gHardwareMipGenSupported = false;
static bool gPointSpritesSupported = false;

bool IsExtensionSupported( const char * Name )
{
   const GLubyte * ExtensionString = glGetString( GL_EXTENSIONS );
   if ( strstr( ( const char * )ExtensionString , Name ) )
   {
      return true;
   }
   return false;
}

void CrossPlatformOpenGL::Initialize()
{
   gSwapIntervalSupported = false;
   gAnistrophyLevelSupported = 0;
   gVertexBuffersSupported = false;
   gMultiTexturingSupported = false;
   gPointSpritesSupported = false;

   // Check for support, and set up the supported flags function pointers.

   const char * GLversion = ( const char * )glGetString( GL_VERSION );

   // Find out if vSync is supported
   p_wglSwapIntervalEXT = ( PFNWGLSWAPINTERVALEXTPROC )wglGetProcAddress( "wglSwapIntervalEXT" );

   if ( p_wglSwapIntervalEXT )
   {
      gSwapIntervalSupported = true;
   }
   else
   {
      gSwapIntervalSupported = false;
   }

   bool gl1_3 = false;
   bool gl1_4 = false;
   bool gl1_5 = false;
   bool gl2_0 = false;
   bool gl2_1 = false;

   if ( GLversion[0] == '1' && GLversion[1] == '.' )
   {
      if ( GLversion[2] >= '3' )
      {
         gl1_3 = true;
      }

      if ( GLversion[2] >= '4' )
      {
         gl1_4 = true;
      }

      if ( GLversion[2] >= '5' )
      {
         gl1_5 = true;
      }

   }

   if ( GLversion[0] == '2' )
   {
      gl1_3 = true;
      gl1_4 = true;
      gl1_5 = true;
      gl2_0 = true;

      if ( GLversion[1] == '.' && GLversion[2] == '1' )
      {
         gl2_1 = true;
      }
   }

   if ( GLversion[0] > '2' )
   {
      gl1_3 = true;
      gl1_4 = true;
      gl1_5 = true;
      gl2_0 = true;
      gl2_1 = true;
   }

   if ( gl2_0 || gl2_1 )
   {

   }

   // Check for anisotrophic filtering support
   if ( IsExtensionSupported( "GL_EXT_texture_filter_anisotropic" ) )
   {
      glGetFloatv( GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT, &gAnistrophyLevelSupported );
   }
   else
   {
      gAnistrophyLevelSupported = 0;
   }

   // Check for vertex buffer support
   if ( IsExtensionSupported( "ARB_vertex_buffer_object" ) || gl1_5 )
   {
      gVertexBuffersSupported = true;

      // If we're using 1.5, then buffers are core features
      if ( gl1_5 )
      {
         ::glBindBuffer = ( PFNGLBINDBUFFERPROC )wglGetProcAddress( "glBindBuffer" );
         ::glDeleteBuffers = ( PFNGLDELETEBUFFERSPROC )wglGetProcAddress( "glDeleteBuffers" );
         ::glGenBuffers = ( PFNGLGENBUFFERSPROC )wglGetProcAddress( "glGenBuffers" );
         ::glIsBuffer = ( PFNGLISBUFFERPROC )wglGetProcAddress( "glIsBuffer" );
         ::glBufferData = ( PFNGLBUFFERDATAPROC )wglGetProcAddress( "glBufferData" );
         ::glBufferSubData = ( PFNGLBUFFERSUBDATAPROC )wglGetProcAddress( "glBufferSubData" );
         ::glGetBufferSubData = ( PFNGLGETBUFFERSUBDATAPROC )wglGetProcAddress( "glGetBufferSubData" );
         ::glMapBuffer = ( PFNGLMAPBUFFERPROC )wglGetProcAddress( "glMapBuffer" );
         ::glUnmapBuffer = ( PFNGLUNMAPBUFFERPROC )wglGetProcAddress( "glUnmapBuffer" );
         ::glGetBufferParameteriv = ( PFNGLGETBUFFERPARAMETERIVPROC )wglGetProcAddress( "glGetBufferParameteriv" );
         ::glGetBufferPointerv = ( PFNGLGETBUFFERPOINTERVPROC )wglGetProcAddress( "glGetBufferPointerv" );
      }
      // Otherwise, buffers are extensions
      else
      {
         ::glBindBuffer = ( PFNGLBINDBUFFERPROC )wglGetProcAddress( "glBindBufferARB" );
         ::glDeleteBuffers = ( PFNGLDELETEBUFFERSPROC )wglGetProcAddress( "glDeleteBuffersARB" );
         ::glGenBuffers = ( PFNGLGENBUFFERSPROC )wglGetProcAddress( "glGenBuffersARB" );
         ::glIsBuffer = ( PFNGLISBUFFERPROC )wglGetProcAddress( "glIsBufferARB" );
         ::glBufferData = ( PFNGLBUFFERDATAPROC )wglGetProcAddress( "glBufferDataARB" );
         ::glBufferSubData = ( PFNGLBUFFERSUBDATAPROC )wglGetProcAddress( "glBufferSubDataARB" );
         ::glGetBufferSubData = ( PFNGLGETBUFFERSUBDATAPROC )wglGetProcAddress( "glGetBufferSubDataARB" );
         ::glMapBuffer = ( PFNGLMAPBUFFERPROC )wglGetProcAddress( "glMapBufferARB" );
         ::glUnmapBuffer = ( PFNGLUNMAPBUFFERPROC )wglGetProcAddress( "glUnmapBufferARB" );
         ::glGetBufferParameteriv = ( PFNGLGETBUFFERPARAMETERIVPROC )wglGetProcAddress( "glGetBufferParameterivARB" );
         ::glGetBufferPointerv = ( PFNGLGETBUFFERPOINTERVPROC )wglGetProcAddress( "glGetBufferPointervARB" );
      }
   }
   else
   {
      gVertexBuffersSupported = false;
   }

   // See if the multitexture extension is supported
   if ( IsExtensionSupported( "GL_ARB_multitexture" ) || gl1_3 )
   {
      gMultiTexturingSupported = true;

      if ( gl1_3 )
      {
         ::glActiveTexture = ( PFNGLACTIVETEXTUREPROC )wglGetProcAddress( "glActiveTexture" );
         ::glClientActiveTexture = ( PFNGLCLIENTACTIVETEXTUREPROC )wglGetProcAddress( "glClientActiveTexture" );
         ::glMultiTexCoord2d = ( PFNGLMULTITEXCOORD2DPROC )wglGetProcAddress( "glMultiTexCoord2d" );
         ::glMultiTexCoord2dv = ( PFNGLMULTITEXCOORD2DVPROC )wglGetProcAddress( "glMultiTexCoord2dv" );
         ::glMultiTexCoord2f = ( PFNGLMULTITEXCOORD2FPROC )wglGetProcAddress( "glMultiTexCoord2f" );
         ::glMultiTexCoord2fv = ( PFNGLMULTITEXCOORD2FVPROC )wglGetProcAddress( "glMultiTexCoord2fv" );
         ::glMultiTexCoord2i = ( PFNGLMULTITEXCOORD2IPROC )wglGetProcAddress( "glMultiTexCoord2i" );
         ::glMultiTexCoord2iv = ( PFNGLMULTITEXCOORD2IVPROC )wglGetProcAddress( "glMultiTexCoord2iv" );
         ::glMultiTexCoord2s = ( PFNGLMULTITEXCOORD2SPROC )wglGetProcAddress( "glMultiTexCoord2s" );
         ::glMultiTexCoord2sv = ( PFNGLMULTITEXCOORD2SVPROC )wglGetProcAddress( "glMultiTexCoord2sv" );
      }
      else
      {
         ::glActiveTexture = ( PFNGLACTIVETEXTUREPROC )wglGetProcAddress( "glActiveTextureARB" );
         ::glClientActiveTexture = ( PFNGLCLIENTACTIVETEXTUREPROC )wglGetProcAddress( "glClientActiveTextureARB" );
         ::glMultiTexCoord2d = ( PFNGLMULTITEXCOORD2DPROC )wglGetProcAddress( "glMultiTexCoord2dARB" );
         ::glMultiTexCoord2dv = ( PFNGLMULTITEXCOORD2DVPROC )wglGetProcAddress( "glMultiTexCoord2dvARB" );
         ::glMultiTexCoord2f = ( PFNGLMULTITEXCOORD2FPROC )wglGetProcAddress( "glMultiTexCoord2fARB" );
         ::glMultiTexCoord2fv = ( PFNGLMULTITEXCOORD2FVPROC )wglGetProcAddress( "glMultiTexCoord2fvARB" );
         ::glMultiTexCoord2i = ( PFNGLMULTITEXCOORD2IPROC )wglGetProcAddress( "glMultiTexCoord2iARB" );
         ::glMultiTexCoord2iv = ( PFNGLMULTITEXCOORD2IVPROC )wglGetProcAddress( "glMultiTexCoord2ivARB" );
         ::glMultiTexCoord2s = ( PFNGLMULTITEXCOORD2SPROC )wglGetProcAddress( "glMultiTexCoord2sARB" );
         ::glMultiTexCoord2sv = ( PFNGLMULTITEXCOORD2SVPROC )wglGetProcAddress( "glMultiTexCoord2svARB" );
      }
   }
   else
   {
      gMultiTexturingSupported = false;
   }

   if ( IsExtensionSupported( "GL_ARB_point_sprite" ) || gl1_5 )
   {
      if ( gl1_5 )
      {
         ::glPointParameterf = ( PFNGLPOINTPARAMETERFPROC )wglGetProcAddress( "glPointParameterf" );
         ::glPointParameteri = ( PFNGLPOINTPARAMETERIPROC )wglGetProcAddress( "glPointParameteri" );
         ::glPointParameterfv = ( PFNGLPOINTPARAMETERFVPROC )wglGetProcAddress( "glPointParameterfv" );
         ::glPointParameteriv = ( PFNGLPOINTPARAMETERIVPROC )wglGetProcAddress( "glPointParameteriv" );
      }
      else
      {
         ::glPointParameterf = ( PFNGLPOINTPARAMETERFPROC )wglGetProcAddress( "glPointParameterfARB" );
         ::glPointParameteri = ( PFNGLPOINTPARAMETERIPROC )wglGetProcAddress( "glPointParameteriARB" );
         ::glPointParameterfv = ( PFNGLPOINTPARAMETERFVPROC )wglGetProcAddress( "glPointParameterfvARB" );
         ::glPointParameteriv = ( PFNGLPOINTPARAMETERIVPROC )wglGetProcAddress( "glPointParameterivARB" );
      }
      gPointSpritesSupported = true;
   }
   else
   {
      gPointSpritesSupported = false;
   }

   if ( IsExtensionSupported( "GL_SGIS_generate_mipmap" ) || gl1_4 )
   {
      gHardwareMipGenSupported = true;
   }
   else
   {
      gHardwareMipGenSupported = false;
   }
}

bool CrossPlatformOpenGL::SufficientFeaturesSupported()
{
   if ( AreVertexBuffersSupported() )
   {
      return true;
   }
   return false;
}

bool CrossPlatformOpenGL::IsVsyncSupported()
{
   return gSwapIntervalSupported;
}
bool CrossPlatformOpenGL::AreVertexBuffersSupported()
{
   return gVertexBuffersSupported;
}

bool CrossPlatformOpenGL::ActivateVsync( bool Activate )
{
   return ( bool )p_wglSwapIntervalEXT( Activate );
}

int CrossPlatformOpenGL::GetSwapInterval()
{
   return p_wglGetSwapIntervalEXT();
}

float CrossPlatformOpenGL::GetMaxAnisotropy()
{
   return gAnistrophyLevelSupported;
}

void CrossPlatformOpenGL::SetAnisotropy( float Level )
{
   glTexParameterf( GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, Level );
}

// Buffer objects :
void CrossPlatformOpenGL::glBindBuffer( GLenum target, GLuint buffer )
{
   ::glBindBuffer( target, buffer );
}
void CrossPlatformOpenGL::glDeleteBuffers( GLsizei n, const GLuint * buffers )
{
   ::glDeleteBuffers( n, buffers );
}
void CrossPlatformOpenGL::glGenBuffers( GLsizei n, GLuint * buffers )
{
   ::glGenBuffers( n, buffers );
}
GLboolean CrossPlatformOpenGL::glIsBuffer( GLuint buffer )
{
   return ::glIsBuffer( buffer );
}

namespace CrossPlatformOpenGL
{
   void glBufferData( GLenum target, GLsizeiptr size, const GLvoid * data, GLenum usage )
   {
      ::glBufferData( target, size, data, usage );
   }
   void glBufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid * data )
   {
      ::glBufferSubData( target, offset, size, data );
   }
   void glGetBufferSubData( GLenum target, GLintptr offset, GLsizeiptr size, GLvoid * data )
   {
      ::glGetBufferSubData( target, offset, size, data );
   }
}

void * CrossPlatformOpenGL::glMapBuffer( GLenum target, GLenum access )
{
   return ::glMapBuffer( target, access );
}
GLboolean CrossPlatformOpenGL::glUnmapBuffer( GLenum target )
{
   return ::glUnmapBuffer( target );
}
void CrossPlatformOpenGL::glGetBufferParameteriv( GLenum target, GLenum value, GLint * data )
{
   ::glGetBufferParameteriv( target, value, data );
}
void CrossPlatformOpenGL::glGetBufferPointerv( GLenum target, GLenum pname, GLvoid ** params )
{
   ::glGetBufferPointerv( target, pname, params );
}

// Multitexturing :
void CrossPlatformOpenGL::glActiveTexture( GLenum texture )
{
   ::glActiveTexture( texture );
}
void CrossPlatformOpenGL::glClientActiveTexture( GLenum texture )
{
   ::glClientActiveTexture( texture );
}
void CrossPlatformOpenGL::glMultiTexCoord2s( GLenum target, GLshort s, GLshort t )
{
   ::glMultiTexCoord2s( target, s, t );
}
void CrossPlatformOpenGL::glMultiTexCoord2i( GLenum target, GLint s, GLint t )
{
   ::glMultiTexCoord2i( target, s, t );
}
void CrossPlatformOpenGL::glMultiTexCoord2f( GLenum target, GLfloat s, GLfloat t )
{
   ::glMultiTexCoord2f( target, s, t );
}
void CrossPlatformOpenGL::glMultiTexCoord2d( GLenum target, GLdouble s, GLdouble t )
{
   ::glMultiTexCoord2d( target, s, t );
}
void CrossPlatformOpenGL::glMultiTexCoord2sv( GLenum target, GLshort * v )
{
   ::glMultiTexCoord2sv( target, v );
}
void CrossPlatformOpenGL::glMultiTexCoord2iv( GLenum target, GLint * v )
{
   ::glMultiTexCoord2iv( target, v );
}
void CrossPlatformOpenGL::glMultiTexCoord2fv( GLenum target, GLfloat * v )
{
   ::glMultiTexCoord2fv( target, v );
}
void CrossPlatformOpenGL::glMultiTexCoord2dv( GLenum target, GLdouble * v )
{
   ::glMultiTexCoord2dv( target, v );
}

bool CrossPlatformOpenGL::ArePointSpritesSupported()
{
   return gPointSpritesSupported;
}

void CrossPlatformOpenGL::glPointParameterf( GLenum pname, GLfloat param )
{
   ::glPointParameterf( pname, param );
}
void CrossPlatformOpenGL::glPointParameteri( GLenum pname, GLint param )
{
   ::glPointParameteri( pname, param );
}


void CrossPlatformOpenGL::glPointParameterfv( GLenum pname, GLfloat * param )
{
   ::glPointParameterfv( pname, param );
}
void CrossPlatformOpenGL::glPointParameteriv( GLenum pname, GLint * param )
{
   ::glPointParameteriv( pname, param );
}

bool CrossPlatformOpenGL::IsHardwareMipGenSupported()
{
   return gHardwareMipGenSupported;
}

void CrossPlatformOpenGL::EnableHardwareMipGen()
{
   glTexParameterf( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE );
}

void CrossPlatformOpenGL::DisableHardwareMipGen()
{
   glTexParameterf( GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_FALSE );
}
